Create by fall on 12 Jun 2022 Recently revised in 17 Apr 2023
二进制对象
总共有五个大对象,Blob
、ArrayBuffer
、File
、FileReader
、TypedArray
Blob
、ArrayBuffer
、File
可以归为一类,它们都是数据;FileReader
、TypedArray
算是工具,用来读取对应数据;
Blob & File
Blob
Blob
全称是 binary large object
,Blob
对象就是一个包含有只读原始数据的类文件对象,通俗讲就是不可修改的二进制文件。在数据库管理系统中,将二进制数据存储为一个单一个体的集合。Blob 通常是影像、声音或多媒体文件。
上传下载通常都是 Blob 对象
创建 Blob 对象
// 第一个参数可以是 ArrayBufferView / Blob / ArrayBuffer / DOMString 等对象构成的数组,其中 DOMString 会被编译为 utf-8
let blobParts = ['<html><h2>Hello Fall</h2></html>']
let options = {type : 'text/html'}
// options:一个可选对象,包含两个属性
// type:默认为 '' 表明放到 blob 中的数组内容的 MIME 类型(见注释)
// endings:用于指定包含行结束符的 `n` 字符串如何被写入,默认为 "transparent" 保持 blob 中的格式不变,可选为 "native",代表行结束符,会被更改为适合宿主操作系统文件系统的换行符
let myBlob = new Blob(blobParts,options)
// 此时 myBlob 是一个对象,拥有 size, type 两个属性
console.log(myBlob.size + " bytes"); // myBlob.size 用字节表示的数据大小(只读)
// 37 bytes
console.log("MIME type is " + myBlob.type); // myBlob.type 表示 MIME 类型(只读)
// MIME 类型为 text/html
MIME:Multipurpose Internet Mail Extensions:多用途互联网邮件扩展类型,用来区分文件的种类。
Blob 上面的方法
// Blob 对象是不可改变的,但是可以通过 slice 进行分割
// start:分割起始点 end:分割终点 contentType:新的MIME类型
Blob.prototype.slice(start, end ,contentType)
// 返回一个能读取 Blob 内容的 ReadableStream
Blob.prototype.stream()
// 返回一个 Promise 对象,且包含 blob 所有内容的
Blob.prototyoe.text()
// 转换为 arrayBuffer 异步转换返回一个 Promise 对象
blob.arrayBuffer().then((res) => {
console.log(res);
});
File
File 对象继承自 Blob,也是二进制对象,通常用在 <input type='file'>
选择的 FileList
对象。或者是拖拽产生的 DataTransfer
对象。
var oneFile = new File(array,name,options)
// array 数据构成的数组
// name 文件名
// options 设置一些属性,Type 属性,lastModified
// File 对象上的属性
oneFile.name// 文件名
oneFile.size// 文件大小
oneFile.lastModified // 最后修改时间的时间戳
oneFile.lastModifiedDate // 最后修改时间的 Date 对象
oneFile.type // MIME类型
方法继承自 Blob
File.prototype.slice(start, end ,contentType)
File.prototype.stream()
File.prototyoe.text()
File.arrayBuffer().then((res) => {
console.log(res);
});
Base64
Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法,它常用于在处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。
浏览器内置了两个方法进行字符串的 Base64
编码和解码
btoa => Binary to ASCII
const str = 'Fall'
// 编码
const b = window.btoa(str)
b // "RmFsbA=="
// 解码
const str2 = window.atob(b)
str2 // "Fall"
用途和特性:
- 将较小的图片转为 DataURL 字符串,直接嵌入网页文本中,同 HTML 或者 JS 文件一并传输,减少请求次数。
- 对数据进行加密(不安全,太容易破解了)
- 编码后体积变大,至少增加 33%,如果过是一个字节,至少变为三个字节
- 编码和解码需要消耗额外工作量
DataURL
URI 是 URL 的一个父类,包括 URL 和 URN
URI(Uniform Resource Identifier):统一资源标识符
URN(Uniform Resource Name):统一资源名称
URL(Uniform Resource Locator):统一资源定位符
DataURL 是一种特殊的 base64 格式,用于表示数据的 URL。可以作为 <img>
标签的 src 属性进行使用。
Data URLs 的数据由四部分组成
前缀:[data:] 指示数据类型的 MIME 类型[<mediatype>]
如果非文本则为可选的 base64 标记:[;base64]、数据本身:,<data>
其中 mediatype 可以为 "image/jpeg" 表示 JPEG 图像文件。
data:[<mediatype>][;base64],<data>
示例:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
ObjectURL
ObjectURL 将一个 file
或者 Blob
类型的数据转换为 UTF-16
的字符串,存在当前操作的 document
下,并存储在内存中。
通过该 URL 可以读取到保存的文件。
类似这样一个链接
blob:http://localhost:3000/53acc2b6-f47b-450f-a390-bf0665e04e59
// 生成 blob url
const blob = new Blob(['ddididid'],{type:'text/plain'})
const file = new File(['fall'],{type:'text/plain'})
const fileURL = URL.createObjectURL(file) // 也可以放入 blob
// 生成的 URL 存储了一个 URL → Blob 映射。
// 数据存储后会常驻内存,只有页面 unload() 事件触发,或者使用手动清除内存占用
URL.revokeObjectURL(fileURL) // 手动清除 document 中的 blob
// URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。
前端下载经常会使用这些内容
const downloadFile = async (params, fileName) => {
// 我们使用axios设置接口返回类型 responseType: "blob", 所以这里从后端返回的是blob。
const results = await download(params);
const a = document.createElement("a");
a.download = fileName + ".xlsx";
// 生成blob url。这里可以使用Blob对象或者File对象
a.href = URL.createObjectURL(results);
document.body.appendChild(a);
a.click();
// 释放内存
URL.revokeObjectURL(a.href);
document.body.removeChild(a);
};
二进制操作
对象关系
Blob
与 ArrayBuffer
的关系
ArrayBuffer
是底层的二进制数据,可以借助工具进行读写。Blob
对象是不可变的,可以接受一个ArrayBuffer
作为参数生成的一个Blob
对象,相当于将ArrayBuffer
封装为一个整体。拿到的是一个整体,你能看到大小,类型,但是不能看到细节。Blob
与ArrayBuffer
对象之间是可以相互转化的:
ArrayBuffer
和 TypedArray
的关系
ArrayBuffer
表示纯 01 构成的串,不知道第一个元素和第二个元素需要如何分割TypedArray
九中类型的构造函数,根据九种类型,去分割ArrayBuffer
的纯 01 串
FileReader
FileReader
可以异步读取存储在用户计算机上(或者原始数据缓冲区)的 Blob
、File
文件,读取为 ArrayBuffer、base64 字符串、文本。
fileReader.readAsArrayBuffer
生成的是 ArrayBufferfileReader.readAsDataURL
生成的是 base64 字符串fileReader.readAsText
生成的是一个文本
使用
// 创建一个 fileReader 对象
const fileReader = new FileReader()
// 属性
fileReader.error // 表示在读取过程中出现错误
fileReader.result // 返回文件的内容,读取完成后,该属性才会生效,result 会根据不同的读取方式有不同的结果
fileReader.readyState // 表示当前文件的读取状态,0:没有加载任何数据,1:数据正在被加载,2:已经完成全部读取请求
方法
// 无论读取成功与否,并不会返回读取的结果,结果会被存储在 fileReader.result 属性中
// 所以一般读取成功后通过 onload 事件,输出 fileReader.result
fileReader.abort() // 终止读取操作,readyState 为 2
fileReader.readAsArrayBuffer() // 将读取的内容转换为 ArrayBuffer
fileReader.readAsBinaryString()
fileReader.readAsDataURL() // 将数据转化为 Base64 的 data url
fileReader.readAsText() // 将二进制数据读取并编码为字符串形式
事件
fileReader.onabort // 读取操作中断时触发
fileReader.onerror // 当出现错误时触发
fileReader.onload // 读取操作完成时触发
fileReader.onloadstart // 读取操作开始时触发
fileReader.onloadend // 读取操作结束时触发,成功或者失败
fileReader.onprogress // 处理 progrress 事件,读取 Blob 时触发
使用
// 读取二进制文件
const blob = new Blob(['hello','everyday'],{type:'text/plain'})
const fileReaderOne = new FileReader()
fileReader.readAsDataURL(blob)
fileReader.onload=()=>{
console.log(fileReader) // 成功后 fileReader.result 是想要的结果
}
<!-- 读取文件 -->
<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
<script>
const loadFile = function(event) {
const reader = new FileReader();
// readAsDataURL 会把文件读取为 base64 的 dataURL
reader.readAsDataURL(event.target.files[0]); // 或者通过 node.files 获取文件
reader.onload = function(){
const output = document.querySelector('#output')
output.src = reader.result; // reader.result 即读取后的内容
};
};
</script>
ArrayBuffer
ArrayBuffer 对象用于表示通用的,固定长度的原始二进制数据。
它是内存上的一段二进制数据,你不能直接操纵 ArrayBuffer 的内容,而是需要创建一个类型化数组对象(TypeArray
)或 DataView
对象进行操作,该对象以特定格式表示缓冲区,并使用该对象读取和写入缓冲区的内容。
Blob
作为一个整体的文件,适合用于传输。只有关注文件的细节修改一部分的数据,才会用到ArrayBuffer
。
// Blob 和 ArrayBuffer 之间相互转化
TypedArray
一组构造函数,一共包含九种类型,每一种都是一个构造函数。
TypedArray
的构造函数接受三个参数,第一个 ArrayBuffer
(其实还可以是数组、视图这里不细说)对象,第二个视图开始的字节号(默认 0),第三个视图结束的字节号(默认直到本段内存区域结束)。
名称 | 占用字节 | 描述 |
---|---|---|
Int8Array | 1 | 8 位有符号整数 |
Uint8Array | 1 | 8 位无符号整数 |
Uint8ClampedArray | 1 | 8 位无符号整型固定数组(数值在0~255之间) |
Int16Array | 2 | 16 位有符号整数 |
Uint16Array | 2 | 16 位无符号整数 |
Int32Array | 4 | 32 位有符号整数 |
Uint32Array | 4 | 32 位无符号整数 |
Float32Array | 4 | 32 位 IEEE 浮点数 |
Float64Array | 8 | 64 位 IEEE 浮点数 |
const buffer = [ 126, 226 ] // 可以是数组、视图、ArrayBuffer
const uint8 = new Uint8Array(buffer); // 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位)。这称为 “8 位无符号整数”。
const uint16 = new Uint16Array(buffer); // 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。
const uint32 = new Uint32Array(buffer);// 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。
const float64 = new Float64Array(buffer);// 将每 8 个字节视为一个 5.0x10-324 到 1.8x10308 之间的浮点数。
DataView
更灵活的视图,DataView 视图支持除 Uint8ClampedArray 以外的八种 TypedArray 类型。
可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口
// 通过传入 buffer 来实现对二进制的读取
const buffer = new ArrayBuffer(8); // 创建八个字节的 ArrayBuffer
const dataView1 = new DataView(buffer);
console.log(dataView1);
dataView1.setInt8(0, 1); // 0 是开始位子 存入1
dataView1.getInt8(0);// 1
view1.setInt8(1, 2); // 1 是开始位子 跳过 8 个位 存入2
dataView1.getInt8(1);// 1
// 读取第二个字节的时候 二进制位 0000 0010 为2
// 通过 16 进制进行读取
dataView1.getUint16() // 258
// 实际上读取的是 0000 0001 0000 0010
互相转换
转换一张图
图片来自文章:js一张图搞定arrayBuffer/Blob/File/fileReader/canvas/base64 的各种转换操作以及文件上传
Blob 和 File
// File 转换为 Blob
let file = new File(['文件'],'fileName.txt',{type:'text/plain'})
let blob = new Blob([file],{type:file.type})
// Blob 转化为 File
let blob = new Blob(['blob文件',{type:'text/plain'}])
let oneFile = new File([Blob],'fileName',{type:blob.type})
转为 base64
也就是转换为 DataURL
// File / Blob 转为 base64
function toDataURL(blob,callBack){
let reader = new FileReader()
reader.readAsDataURL(blob)
reader.onload = function (e){callBack(e.target.result)}
}
const blob = new Blob(['hello','fall'],{type:'text/plain'})
const file = new File(['byte','me'],'allright')
toDataURL(blob,(result)=>{
console.log(result)
})
toDataURL(file,(result)=>{
console.log(result)
})
img
转为 Base64
const imgToBase64 = (imgURL)=>{
let image = new Image()
image.src = imgURL
return new Promise(resolve=>{
image.onload=()=>{
let canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
let context = canvas.getContext('2d')
context.drawImage(image,0,0,image.width,image.height)
let dataURL = canvas.toDataURL('image/png')
resolve(dataURL)
}
})
}
imgToBase64("../vue2/src/assets/logo.png").then((res) => {
console.log(res);
});
Base64 转换
转为 Blob
function dataURLtoBlob(dataurl) {
// `data:[<mediatype>][;base64],<data>`
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
转为 File
function dataURLtoFile(dataurl, filename) {
//将base64转换为文件
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}
Blob 和 ArrayBuffer
将二进制内容转换为可以读写的 ArrayBuffer
const name = 'fall'
const blob = new Blob(name,{type:'text/plain'})
function readBlob (blob, type) {
return new Promise(resolve => {
const reader = new FileReader()
reader.readAsArrayBuffer(blob)
reader.onload = function (e) {
resolve(e.target.result)
}
})
}
readBlob(blob).then(res=>{
new Unit8Array(res)
})
使用场景
下载文件
生成二进制文件,然后下载
const download = (fileName, blob) => {
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
link.remove();
URL.revokeObjectURL(link.href);
};
const downloadBtn = document.querySelector("#downloadBtn");
downloadBtn.addEventListener("click", (event) => {
const fileName = "blob.txt";
const myBlob = new Blob(["一文彻底掌握 Blob Web API"], { type: "text/plain" });
download(fileName, myBlob);
});
生成 DataURL,然后下载
function downloadJSON(downloadData){
let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(downloadData));
let link = document.createElement('a')
link.setAttribute("href", dataStr);
link.setAttribute("download", "文件名.json")
link.click();
link.remove();
}
downloadJSON({
name:"Fall",
age:"23"
})
图片压缩
在前端要实现图片压缩,我们可以利用 Canvas 对象提供的 toDataURL()
方法,该方法接收 type
和 encoderOptions
两个可选参数。
// compress.js
const MAX_WIDTH = 800; // 图片最大宽度
const MAX_HEIGHT = 800; // 图片最大高度
// 分别传入 DataURL,图片质量,最高为 100,mime 类型
function compressImg(base64, quality, mimeType) {
let canvas = document.createElement("canvas");
let img = document.createElement("img");
img.crossOrigin = "anonymous";
return new Promise((resolve, reject) => {
img.src = base64
img.onload = () => {
let targetWidth, targetHeight;
if (img.width > MAX_WIDTH) {
targetWidth = MAX_WIDTH;
targetHeight = (img.height * MAX_WIDTH) / img.width;
} else {
targetWidth = img.width;
targetHeight = img.height;
}
canvas.width = targetWidth;
canvas.height = targetHeight;
let ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除画布
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
let imageData = canvas.toDataURL(mimeType, quality / 100);
resolve(imageData);
}
})
}
生成 PDF
import jspdf from 'jspdf'
function generatePdf() {
const js2pdf = new jsPDF();
js2pdf.text("Hello semlinker!", 66, 88);
const blob = new Blob([js2pdf.output()], { type: "application/pdf" });
blob.text().then((blobAsText) => {
console.log(blobAsText);
});
}
预览图片
FileReader API,我们也可以方便的实现图片本地预览功能,具体代码如下:
<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
<script>
const loadFile = function(event) {
const reader = new FileReader();
// readAsDataURL 会把文件读取为 base64 的 dataURL
reader.readAsDataURL(event.target.files[0]);
reader.onload = function(){
const output = document.querySelector('#output')
output.src = reader.result;
};
};
</script>
检查文件类型
对于不同类型的文件,初始的几个字节内容都是固定的。
文件都是三十二位,二进制存储。比如说,D4
就是 1 Byte,也就是 8 Bit。
文件类型 | 文件后缀 | 魔数 |
---|---|---|
JPEG | jpg/jpeg | 0xFF D8 FF |
PNG | png | 0x89 50 4E 47 0D 0A 1A 0A |
GIF | gif | 0x47 49 46 38(GIF8) |
BMP | bmp | 0x42 4D |
0x25 50 44 46 对应的字符串为(%PDF) |
问题来了,一个文件,开头是 89 50 4E 47
,那么它是什么类型文件?查表可知:png
function readBuffer(file,start=0,end=2){
return new Promise((resolve,reject)=>{
const reader = new FileReader()
reader.onerror = rejcet
reader.onload=()=>{
resolve(reader.result)
}
reader.readAsArrayBuffer(file.slice(start,end))
})
}
function check(headers){
return (buffers,options={offset:0})=>headers.every((header,index)=>header===buffers[options.offset+index])
}
// 高阶函数,生成对应的函数
// 翻译验证对应的函数
// 比如说,生成检查 PNG 的函数
const isPNG = check([0x98,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a])
有一个检查类型的库:file-type